import pytest


@pytest.mark.requires_dynamic_linking
def test_uncaught_cpp_exceptions(selenium):
    assert selenium.run_js(
        """
        await pyodide.loadPackage("cpp-exceptions-test");
        const Tests = pyodide._api.tests;
        const throwlib = pyodide._module.LDSO.loadedLibsByName["/usr/lib/cpp-exceptions-test-throw.so"].exports;
        function t(x){
            try {
                throwlib.throw_exc(x);
            } catch(e){
                let errString = Tests.convertCppException(e).toString();
                errString = errString.replace(/[0-9]+/, "xxx");
                return errString;
            }
        }
        return [t(1), t(2), t(3), t(4), t(5)];
        """
    ) == [
        "CppException int: The exception is an object of type int at address xxx "
        "which does not inherit from std::exception",
        "CppException char: The exception is an object of type char at address xxx "
        "which does not inherit from std::exception",
        "CppException std::runtime_error: abc",
        "CppException myexception: My exception happened",
        "CppException char const*: The exception is an object of type char const* at "
        "address xxx which does not inherit from std::exception",
    ]


@pytest.mark.requires_dynamic_linking
def test_cpp_exception_catching(selenium):
    assert selenium.run_js(
        """
        await pyodide.loadPackage("cpp-exceptions-test");
        const Module = pyodide._module;
        const catchlib = pyodide._module.LDSO.loadedLibsByName["/usr/lib/cpp-exceptions-test-catch.so"].exports;
        function t(x){
            const ptr = catchlib.catch_exc(x);
            const res = Module.UTF8ToString(ptr);
            Module._free(ptr);
            return res;
        }
        return [t(1), t(2), t(3), t(5)];
        """
    ) == [
        "caught int 1000",
        "caught char 99",
        "caught runtime_error abc",
        "caught ????",
    ]


@pytest.mark.requires_dynamic_linking
def test_sjlj(selenium):
    assert (
        selenium.run_js(
            """
            await pyodide.loadPackage("cpp-exceptions-test");
            const Module = pyodide._module;
            const catchlib = pyodide._module.LDSO.loadedLibsByName["/usr/lib/cpp-exceptions-test-catch.so"].exports;
            return catchlib.set_jmp_func();
            """
        )
    ) == 5


@pytest.mark.requires_dynamic_linking
def test_cpp_exception_catching_invoke(selenium):
    """Test invoke functions. See explanation in catch.cpp"""

    assert selenium.run_js(
        """
        await pyodide.loadPackage("cpp-exceptions-test");
        const Module = pyodide._module;
        const catchlib = pyodide._module.LDSO.loadedLibsByName["/usr/lib/cpp-exceptions-test-catch.so"].exports;
        function t(x){
            const ptr = catchlib.catch_invoke_func(x);
            const res = Module.UTF8ToString(ptr);
            Module._free(ptr);
            return res;
        }
        return [t(0), t(1)];
        """
    ) == [
        "caught runtime_error standard invoke 1 2",
        "caught runtime_error custom invoke 1 2 3 4 5 6 7",
    ]
